"""
贪吃蛇游戏

1. 绘制了背景、网格
2. 绘制了蛇身、食物
3. 处理用户按键输入
4. 处理蛇吃食物
5. 处理画面fps，速度更新

----------------待完成
墙体碰撞，game_over √
从屏幕另一端出现 √
碰到身体，game_over √
分数高了，游戏要变快 √
食物不能出现在身体上 √

"""
import random
import sys
import time
import pygame

COLOR_RED = (255, 0, 0)

SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480
BLOCK_SIZE = 20

COLOR_GRAY = (150, 150, 150)
COLOR_GREEN = (0, 150, 0)
COLOR_WHITE = (255, 255, 255)

DIRECTION_MOVE = {
    pygame.K_DOWN: (0, 1),  # down
    pygame.K_RIGHT: (1, 0),  # right
    pygame.K_UP: (0, -1),  # up
    pygame.K_LEFT: (-1, 0)  # left
}
DIRECTION_HEAD_ANGLE = {
    pygame.K_DOWN: 0,
    pygame.K_RIGHT: 90,
    pygame.K_UP: 180,
    pygame.K_LEFT: 270
}


class Snake:
    """
        # 0, 90, 180, 270
        # 0, 1 ,  2 , 3
        #下, 右 , 上 , 左
    """

    def __init__(self, x, y):
        self.direction = pygame.K_RIGHT  # 右
        self.score = 0
        self.cross_wall_enable = True

        snake_head = pygame.image.load("img/head-red.png")
        # 缩放
        self.snake_head_image = pygame.transform.scale(snake_head, (BLOCK_SIZE, BLOCK_SIZE))

        self.snake_body = [
            pygame.Rect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
        ]
        for _ in range(3):
            self.grow()

    def draw(self, screen):
        # 绘制身体
        for node in self.snake_body[1:]:
            # 绘制矩形 (x, y, w, h)
            pygame.draw.rect(screen, COLOR_WHITE, node, border_radius=3)

        # 绘制蛇头
        head_rect: pygame.Rect = self.snake_body[0]  # 取出蛇头
        snake_head = pygame.transform.rotate(self.snake_head_image, DIRECTION_HEAD_ANGLE[self.direction])  # 旋转蛇头
        screen.blit(snake_head, (head_rect.x, head_rect.y))

    def move(self):
        """
        让蛇根据当前方向进行移动（移动一格） prompt
        """
        # 把蛇头往前进的方向复制一份
        new_node = self.snake_body[0].copy()
        new_move = DIRECTION_MOVE[self.direction]  # 取出对应方向的x, y变化格子数
        new_node.x += (new_move[0] * BLOCK_SIZE)
        new_node.y += (new_move[1] * BLOCK_SIZE)

        if self.cross_wall_enable:
            # 如果超出了边缘进行瞬移
            if new_node.x >= SCREEN_WIDTH:
                new_node.x -= SCREEN_WIDTH
            elif new_node.x < 0:
                new_node.x += SCREEN_WIDTH

            if new_node.y >= SCREEN_HEIGHT:
                new_node.y -= SCREEN_HEIGHT
            elif new_node.y < 0:
                new_node.y += SCREEN_HEIGHT

        # 把新的头放到最前边
        self.snake_body.insert(0, new_node)

        # 把蛇尾移除掉
        self.snake_body.pop()  # 默认删除末尾数

    def is_direction_enable(self, event_key):
        # 判断event_key是否是合法的按键，上下左右
        if event_key not in DIRECTION_MOVE.keys():
            return False
        # 蛇不能直接原地掉头
        # 不能左右掉头
        LR = (pygame.K_LEFT, pygame.K_RIGHT)
        if event_key in LR and self.direction in LR:
            return False
        # 不能上下掉头
        UD = (pygame.K_UP, pygame.K_DOWN)
        if event_key in UD and self.direction in UD:
            return False

        return True

    def update_direction(self, event_key):
        self.direction = event_key

    def grow(self):
        # 蛇尾复制一份
        new_node = self.snake_body[-1].copy()
        # 添加到尾部
        self.snake_body.append(new_node)
        # 加一分
        self.score += 1


class Food:
    """
    食物
    """

    def __init__(self, rect):
        # x = random.randint(0, SCREEN_WIDTH // BLOCK_SIZE - 1)  # 0, 1, 2, ... 31  (640)
        # y = random.randint(0, SCREEN_HEIGHT // BLOCK_SIZE - 1)  # 0, 1, 2, ... 23 (480)
        # self.rect = pygame.Rect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
        self.rect = rect

    def draw(self, screen):
        pygame.draw.rect(
            screen, (0, 0, 255),  # 颜色
            self.rect,  # 位置、大小的矩形
            border_radius=3
        )

    @staticmethod
    def gen_food_position(snake: Snake):
        """
        根据蛇身体，随机生成食物坐标，如果生成到蛇body上，重新生成
        :param snake:
        :return: x,y食物的坐标
        """
        while True:
            x = random.randint(0, SCREEN_WIDTH // BLOCK_SIZE - 1)  # 0, 1, 2, ... 31  (640)
            y = random.randint(0, SCREEN_HEIGHT // BLOCK_SIZE - 1)  # 0, 1, 2, ... 23 (480)
            new_food = pygame.Rect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
            if new_food not in snake.snake_body:
                return new_food


class Game:

    def __init__(self, title):
        # 游戏初始化
        pygame.init()
        # 设置窗口大小，得到窗口对象
        self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
        # 设置标题
        pygame.display.set_caption(title)
        # 设置icon
        pygame.display.set_icon(pygame.image.load("img/icon.png"))
        # 加载一次背景图(缩放)
        bg_image = pygame.image.load("img/bg.png")
        self.bg_image = pygame.transform.scale(bg_image, (SCREEN_WIDTH, SCREEN_HEIGHT))

    def start(self):
        snake = Snake(3, 3)
        food = Food(Food.gen_food_position(snake))
        clock = pygame.time.Clock()
        is_game_over = False

        while True:
            # 1.处理事件--------------------------------------------------------
            new_direction = None
            # 两帧之间，用户可能会按下多个按钮（比如快速的按 上、下）
            event_list = pygame.event.get()
            for event in event_list:
                if event.type == pygame.QUIT:
                    # 退出游戏
                    self.exit_game()
                if event.type == pygame.KEYDOWN:
                    # 键盘按下
                    if event.key == pygame.K_ESCAPE:  # key ESC
                        self.exit_game()
                        return
                    elif is_game_over and event.key == pygame.K_SPACE:
                        # 重启游戏
                        snake = Snake(3, 3)
                        food = Food(Food.gen_food_position(snake))
                        is_game_over = False
                        # self.start()
                        # return
                    elif snake.is_direction_enable(event.key):
                        # 只让最后一个运动操作生效。上下左右
                        new_direction = event.key

            if new_direction is not None:
                snake.update_direction(new_direction)

            # 2.游戏事件--------------------------------------------------------
            if not is_game_over:
                snake.move()

                # 判断蛇是否吃到了食物
                if snake.snake_body[0] == food.rect:
                    # 吃到食物，刷新食物
                    food = Food(Food.gen_food_position(snake))
                    # 蛇的长一节
                    snake.grow()

                # 判断蛇身是否越界（四周边界）
                snake_head = snake.snake_body[0]
                if snake_head.x < 0 or snake_head.x >= SCREEN_WIDTH or snake_head.y < 0 or snake_head.y >= SCREEN_HEIGHT:
                    is_game_over = True

                # 判断蛇头是否碰到了蛇身
                for block in snake.snake_body[1:]:
                    if snake_head == block:
                        is_game_over = True

            # 3.绘制画面--------------------------------------------------------
            # self.screen.fill((0, 255, 0))
            # 绘制背景图
            self.screen.blit(self.bg_image, (0, 0))
            # 绘制网格
            # 绘制所有横线 h = 640 // 20 = 24  [0, 20, 40, 60, ... 640)
            for y in range(0, SCREEN_HEIGHT, BLOCK_SIZE):
                pygame.draw.line(self.screen, COLOR_GRAY, (0, y), (SCREEN_WIDTH, y))
            # 绘制所有竖线 v = 480 // 20 = 24  [0, 20, 40, 60, ... 480)
            for x in range(0, SCREEN_WIDTH, BLOCK_SIZE):
                pygame.draw.line(self.screen, COLOR_GRAY, (x, 0), (x, SCREEN_HEIGHT))

            # 绘制蛇身蛇头
            snake.draw(self.screen)
            # 绘制食物
            food.draw(self.screen)

            fps = clock.get_fps()  # 获取真实每秒帧率
            # 将fps绘制到屏幕右上角
            self.show_text("FPS: {:.2f}".format(fps), 20, SCREEN_WIDTH - 100, 10)
            # 将snake的score绘制到屏幕左上角
            self.show_text("Score: {}".format(snake.score), 20, 10, 10)

            if is_game_over:
                # 显示Game Over, 得分, 按任意键重新开始, 按Q退出游戏
                self.show_text("游戏结束", 40, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4)
                self.show_text("得分: {}".format(snake.score), 20, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4 + 50)
                self.show_text("按任意键重新开始", 20, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4 + 100)
                self.show_text("按Q退出游戏", 20, SCREEN_WIDTH // 4, SCREEN_HEIGHT // 4 + 150)

            # 4.刷新-----------------------------------------------------------
            # 刷新整个屏幕、窗体
            # pygame.display.flip()
            # 刷新指定区域（效率更高）参数不传时同flip
            pygame.display.update()

            # 5.控制节奏---------------------------------------------------------
            # time.sleep(0.1) # 每隔0.1秒刷新一次
            speed = min(snake.score // 2, 30)
            clock.tick(10 + speed)  # 设置游戏节奏为10（每秒刷新10次）作用等同于time.sleep,但是更精确，因为每次绘制的时间不一样

    def show_text(self, text, font_size, x, y):
        font = pygame.font.SysFont("SimHei", font_size)
        text = font.render(text, True, COLOR_RED)
        self.screen.blit(text, (x, y))

    @staticmethod
    def exit_game():
        pygame.display.quit()
        sys.exit()


game = Game("贪吃蛇v1.0")
game.start()
